iT邦幫忙

2023 iThome 鐵人賽

DAY 20
0
Security

從自建漏洞中學習 - 一起填坑吧系列 第 20

Auth 應用程式 - Authentication 認證篇

  • 分享至 

  • xImage
  •  

Auth 應用程式 - Authentication 認證篇

JWT - Quick Review

在前面我們實踐了 Login 並取得 JWT

接著,在 Access 時,我們會需要 Send 一個 Auth request 給 Server,然後,Server 端會比較 JWT,最後回傳 response 給 Client 端。

https://ithelp.ithome.com.tw/upload/images/20231005/201071975AApQEIPbJ.png

Authentication 實踐篇

在 access 的時候,會將 JWT 帶入 request header,接著,我們會藉由在 server 中比較 JWT,再返回 request。

加入 JWT 的四步驟

  1. 取得 token

    接收 Client 傳來的 header ,並取得 JWT

  2. 驗證 token

  3. 確認 user 是否還存在

    避免在認證完 token 後,user 被 delete

  4. 確認是否在 issue 發出去後,user 改動了 password

    Password 被更動,應該要重新取得 JWT

  5. 允許進入被受保護的 route

以下為範例:

Good Practice

protect router

// 會先運行 authController.protect,執行成功後再運行 testController.getAllTests (此處主要是保護 getAllTests 這個 routes)
router
  .route('/')
  .get(authController.protect, testController.getAllTests)
  .post(testController.createTest)

protect function:

exports.protect = async (req, res, next) => {
    
    // 1. 取得 token 
    let token;

    // 此處會依照開頭是否是 Bearer 來作為判斷依據的原因:
    // 因為這邊預設 request 的 header 是帶上: "Bearer ${JWT_TOKEN}" 
    if(
      req.headers.authentication &&
      req.headers.autrhntication.startWith('Bearer')
    ) {
        token = req.headers.authorization.split(' ')[1];
    }
    
    if (!token) {
        return next (
            new AppError('Please Log in to get access', 401);
        );
    }
    
    // 2. 驗證 token
    // 此處使用 promisify 是想要把 jwt.verify 轉換成 promise 版本,避免 Error first call-back
    const decoded = await promisify(jwt.verify)(token, process.env.JWT_SECRET);
    
    
    // 3. 確認 user 是否還存在
    const freshUser = await User.findById(decoded.id);
    if(!freshUser) {
        return next(new AppError('The token belonging to this user does no longer exist.', 401));
    }
    
    // 4. 確認是否在 token 發出去後,user 改動了 password
    // 此處使用到的 changedPasswordAfter function 寫在下一個程式碼區塊
    // 這邊的 iat 是指 指示 JWT 發佈時間的時間戳,有興趣可以回之前的規劃篇看看 ~
    if (freshUser.changedPasswordAfter(decoded.iat)) {
        return next(new AppError('Password has been changed recently. Please login again.', 401));
    }
    
    // 5. 允許進入被受保護的 route
    req.user = freshUser;
    
    next();
    
}

changedPasswordAfter() function:

userSchema.methods.changedPasswordAfter = function(JWTTimestamp) {
    
    // 此處為原本有的 passwordChangedAt 參數
    if (this.passwordChangedAt) {
        
        const changedTimestamp =   parseInt(
            this.passwordChangedAt.getTime() / 1000, 10);
        
        return JWTTimestamp = changedTimestamp;

    }
    
    return false;
}

Bad Practice

在上述程式碼中,我們並沒有特別處理 Error 的管理部分,
所以當出現了非預期的 Error,可能就整個系統 crash 了!

回顧我們先前的文章,還記得 Error Handling 的重要性吧XD

我們在上述程式碼說明了 JWT 的驗證實作,但我們都沒有處理 Error Handling! 這會是一個致命傷。

我們將會在下次一次說明 Error Handling 的問題,production 環境 & 非 production 環境 error message 的差異 ~


今日小心得

下次 Error Handling 感覺又可以寫一篇,不過感覺自己越學習越多越充實~ 雖然經驗還沒有很足,但我想可以越了解越多也是好事 :D


Reference


上一篇
Auth 應用程式 - Login API 實踐篇
下一篇
Auth 應用程式 - Error 處理篇
系列文
從自建漏洞中學習 - 一起填坑吧30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言